上一篇【以Rust開發一個網站,不是網頁喔!】介紹以Rust開發一個網站,這次我們再聊聊【Rust如何與Python整合】。
Rust與Python整合的方式有兩種:
以下我們就分別以簡單的範例說明。
越來越多的Python套件改用Rust語言開發,例如:
以Polars為例,讀入檔案的速度約比Pandas快7倍,又因為Rust提供【跨語言呼叫介面】(Foreign Function Interface, FFI),因此,很多Rust開發的套件都同時支援Rust/Python API介面,以下我們來實作一下。
Pyo3套件專門提供Rust/Python相互呼叫的函數庫,包覆FFI相關的支援,包括資料型別的轉換及函數呼叫的規定,因此,我們就直接以Pyo3套件實作。
因為,Rust效能遠比Python好,因此,常常會把需要注重效能的商業邏輯以Rust開發,例如大量資料需以迴圈逐筆計算,使用Rust不僅效能較好,記憶體使用也較節省。
以下實作階層(factorial)計算,程序如下:
pip install maturin
maturin init
選擇Pyo3為骨架,並設專案名稱為test1。
cargo add pyo3
use pyo3::prelude::*;
// 階層(factorial)計算
#[pyfunction]
fn factorial(a: i32) -> PyResult<i32> {
let mut sum1:i32 = 1;
for i in 1..(a+1) {
sum1 = sum1 * i;
}
Ok(sum1)
}
// 註冊factorial函數
#[pymodule]
fn test1(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(factorial, m)?)?;
Ok(())
}
maturin develop
import test1
test1. factorial(5)
會得到12345 = 120。
就是那麼簡單,含兩個步驟:建立Rust函數,並註冊函數即可。
相反的,也可以使用Rust呼叫Python,但什麼時候會用到呢? Rust畢竟還是相對年輕的語言,套件功能還不那麼完整,例如想要繪製統計圖,使用Python只要幾行就搞定了,以下就來實驗這個情境。
cargo new call_python
Python::with_gil(|py| {
// Deprecated since 0.23.0: renamed to PyModule::from_code
let fun: Py<PyAny> = PyModule::from_code_bound(
py,
"def example(*args, **kwargs):
if args != ():
print('called with args', args)
if kwargs != {}:
print('called with kwargs', kwargs)
if args == () and kwargs == {}:
print('called with no arguments')",
"",
"",
)?
.getattr("example")?
.into();
使用PyModule::from_code_bound包覆Python程式碼,上面範例很簡單,就是接收各種參數類別,包括一般參數(*args)及命名參數(**kwargs),並列印出來。
// call object without any arguments
fun.call0(py)?;
// pass object with Rust tuple of positional arguments
let arg1 = "arg1";
let arg2 = "arg2";
let arg3 = "arg3";
let args = (arg1, arg2, arg3);
fun.call1(py, args)?;
// call object with Python tuple of positional arguments
let args = PyTuple::new_bound(py, &[arg1, arg2, arg3]);
fun.call1(py, args)?;
set PYTHONHOME=C:\Users\<使用者帳號>\anaconda3
set PYTHONPATH=C:\Users\<使用者帳號>\anaconda3
cargo run
called with no arguments
called with args ('arg1', 'arg2', 'arg3')
called with args ('arg1', 'arg2', 'arg3')
import matplotlib.pyplot as plt
import seaborn as sns
# Load an example dataset with long-form data
df = sns.load_dataset("tips")
# Plot the responses for different events and regions
fig, ax = plt.subplots(figsize=(8, 6))
sns.barplot(x="day", y="tip", hue="day", data=df)
ax.get_legend().remove()
plt.show()
let code = std::fs::read_to_string("example.py").unwrap();
let _ = Python::with_gil(|py| -> PyResult<()> {
PyModule::from_code_bound(py, &code, "example.py", "example")?;
Ok(())
});
執行cargo run,輸出結果:
將example.py置換如下,並先安裝nicegui套件,它是網頁開發套件。
from pathlib import Path
from nicegui import app, ui
from nicegui.events import KeyEventArguments
folder = Path(__file__).parent / 'slides' # image source: https://pixabay.com/
files = sorted(f.name for f in folder.glob('*.jpg'))
index = 0
def handle_key(event: KeyEventArguments) -> None:
global index
if event.action.keydown:
if event.key.arrow_right:
index += 1
if event.key.arrow_left:
index -= 1
index = index % len(files)
slide.set_source(f'slides/{files[index]}')
app.add_static_files('/slides', folder) # serve all files in this folder
slide = ui.image(f'slides/{files[index]}') # show the first image
ui.keyboard(on_key=handle_key) # handle keyboard events
ui.run(port=8000)
以上範例說明如何進行跨語言呼叫,結合Rust及Python優點,既可提升系統效能,又可提升編程生產力,下一次將繼續說明Rust與AI的整合。
Rust雖然具備諸多優點,但學習曲線陡峭,因此筆者最近剛完成【Rust 最佳入門與實戰】一書的撰寫,希望能與讀者分享Rust開發心得,內容除了Rust語言的入門、設計典範(Design patterns)外,也著重應用的探討,包括網頁、WebAssembly、桌面程式、資料庫、機器學習/深度學習、區塊鏈…等。
本文相關範例放在這裡,【Rust 最佳入門與實戰】還有各式各樣的範例供大家下載。